clojure transducers
这个也是前两天看的,当时让两个llm给我解释transducers,然后我一个字都看不懂,急得我满头大汗……这时候我去看了下clojure原文档,发现还是原文档写得明白。
首先要知道什么是reducer。reducer就是类似于 sum acc xs = 把列表xs里的内容统统加到acc上,返回acc 这样的函数。
还有如 filter pred xs = 看看xs列表里的内容符不符合pred(一个predicate),符合的话放进新列表,不符合的踢掉,最后返回新列表。
还有经典的map f xs = 把f apply到xs里所有元素,返回新列表。
感觉还是画个图比较直观:
(我一直都觉得,在解释函数式编程的时候使用linear notation,是比较反人类的。)
这里我本应该说:reducer属于xxx category,之类的话,但我不知道xxx应该是什么。可能是kleisli category吧。
那么,有的时候我们会想要把多个reducers套在一起。例如
- 先map一个(inc 1)——列表里所有数+1
- 然后filter去掉偶数
- 然后sum
最后返回一个整数。
在这个过程中,map会产生一个新list,filter又会产生一个新list,因为这些都不是in place操作。
有人就说,不能做一个新reducer map_inc_1_filter_even_sum 吗,这样我们只需要对每个元素做以下操作:
- 加1
- 看看是不是偶数,是的话丢掉
- 把这个数加到acc里
这样最后会是一样的结果。
那么transducer就是clojure搞出来的一个“把reducer打包在一起的工具”。
字面意思就是reducer transformer。
我们看一下文档:
(def xf
(comp
(filter odd?)
(map inc)
(take 5)))
xf是一个transducer(x for trans),还需要传入一个reducer,才会得到一个reducer。
然后以xf rf 参数这样的形式去使用,rf是最后一个reducer,会最后一个执行。
这里的关键是,在clojure里,像(filter odd?)这样只有单个参数的reducer,其实是一个transducer,而不是等待curry的reducer!
而transducer的signature是:
transducer :: reducer -> reducer
我偷懒了,把reducer当成一个type写出来。
总之transducer会拿到一个reducer作为参数,变形一下,然后返回一个新reducer。
所以,当xf rf的时候:
- comp发力了!
- 先用
(take 5)这个transducer去改变rf,返回一个reducer_take5_rf - 然后用
(map inc)这个transducer去改变reducer_take5_rf,返回一个reducer_mapinc_take5_rf - 然后用
(filter odd?)这个transducer去改变reducer_mapinc_take5_rf,得到最终的reducerreducer_filterodd_mapinc_take5_rf
这里我也想画图,但没有手绘板,还是算了。
在学数学的时候,有一个白板真的很重要。